// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable CommentTypo
// ReSharper disable InconsistentNaming

/* HsbColor.cs --
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Drawing;

#endregion

#nullable enable

namespace AM.Drawing.Charting;

/// <summary>
/// Hue-Saturation-Brightness Color class to store a color value,
/// and to manage conversions
/// to and from RGB colors in the <see cref="Color" /> struct.
/// </summary>
/// <remarks>
/// This class is based on code from http://www.cs.rit.edu/~ncs/color/ by Eugene Vishnevsky.
/// This struct stores the hue, saturation, brightness, and alpha values internally as
/// <see cref="byte" /> values from 0 to 255.  The hue represents a fraction of the 360 degrees
/// of color space available. The saturation is the color intensity, where 0 represents gray scale
/// and 255 is the most colored.  For the brightness, 0 represents black and 255
/// represents white.
/// </remarks>
[Serializable]
public struct HsbColor
{
    /// <summary>
    /// The color hue value, ranging from 0 to 255.
    /// </summary>
    /// <remarks>
    /// This property is actually a rescaling of the 360 degrees on the color wheel to 255
    /// possible values.  Therefore, every 42.5 units is a new sector, with the following
    /// convention:  red=0, yellow=42.5, green=85, cyan=127.5, blue=170, magenta=212.5
    /// </remarks>
    public byte H;

    /// <summary>
    /// The color saturation (intensity) value, ranging from 0 (gray scale) to 255 (most colored).
    /// </summary>
    public byte S;

    /// <summary>
    /// The brightness value, ranging from 0 (black) to 255 (white).
    /// </summary>
    public byte B;

    /// <summary>
    /// The alpha value (opacity), ranging from 0 (transparent) to 255 (opaque).
    /// </summary>
    public byte A;

    /// <summary>
    /// Constructor to load an <see cref="HsbColor" /> struct from hue, saturation and
    /// brightness values
    /// </summary>
    /// <param name="h">The color hue value, ranging from 0 to 255</param>
    /// <param name="s">The color saturation (intensity) value, ranging from 0 (gray scale)
    /// to 255 (most colored)</param>
    /// <param name="b">The brightness value, ranging from 0 (black) to 255 (white)</param>
    public HsbColor (int h, int s, int b)
    {
        H = (byte)h;
        S = (byte)s;
        B = (byte)b;
        A = 255;
    }

    /// <summary>
    /// Constructor to load an <see cref="HsbColor" /> struct from hue, saturation,
    /// brightness, and alpha values
    /// </summary>
    /// <param name="h">The color hue value, ranging from 0 to 255</param>
    /// <param name="s">The color saturation (intensity) value, ranging from 0 (gray scale)
    /// to 255 (most colored)</param>
    /// <param name="b">The brightness value, ranging from 0 (black) to 255 (white)</param>
    /// <param name="a">The alpha value (opacity), ranging from 0 (transparent) to
    /// 255 (opaque)</param>
    public HsbColor (int a, int h, int s, int b)
        : this (h, s, b)
    {
        A = (byte)a;
    }

    /// <summary>
    /// Constructor to load an <see cref="HsbColor" /> struct from a system
    /// <see cref="Color" /> struct.
    /// </summary>
    /// <param name="color">An rgb <see cref="Color" /> struct containing the equivalent
    /// color you want to generate</param>
    public HsbColor (Color color)
    {
        this = FromRGB (color);
    }


    /// <summary>
    /// Implicit conversion operator to convert directly from an <see cref="HsbColor" /> to
    /// a <see cref="Color" /> struct.
    /// </summary>
    /// <param name="hsbColor">The <see cref="HsbColor" /> struct to be converted</param>
    /// <returns>An equivalent <see cref="Color" /> struct that can be used in the GDI+
    /// graphics library</returns>
    public static implicit operator Color (HsbColor hsbColor)
    {
        return ToRGB (hsbColor);
    }

    /// <summary>
    /// Convert an <see cref="HsbColor" /> value to an equivalent <see cref="Color" /> value.
    /// </summary>
    /// <remarks>
    /// This method is based on code from http://www.cs.rit.edu/~ncs/color/ by Eugene Vishnevsky.
    /// </remarks>
    /// <param name="hsbColor">The <see cref="HsbColor" /> struct to be converted</param>
    /// <returns>An equivalent <see cref="Color" /> struct, compatible with the GDI+ library</returns>
    public static Color ToRGB (HsbColor hsbColor)
    {
        Color rgbColor = Color.Black;

        // Determine which sector of the color wheel contains this hue
        // hsbColor.H ranges from 0 to 255, and there are 6 sectors, so 42.5 per sector
        int sector = (int)Math.Floor ((double)hsbColor.H / 42.5);

        // Calculate where the hue lies within the sector for interpolation purpose
        double fraction = (double)hsbColor.H / 42.5 - (double)sector;

        double sFrac = (double)hsbColor.S / 255.0;
        byte p = (byte)(((double)hsbColor.B * (1.0 - sFrac)) + 0.5);
        byte q = (byte)(((double)hsbColor.B * (1.0 - sFrac * fraction)) + 0.5);
        byte t = (byte)(((double)hsbColor.B * (1.0 - sFrac * (1.0 - fraction))) + 0.5);


        switch (sector)
        {
            case 0: // red - yellow
                rgbColor = Color.FromArgb (hsbColor.A, hsbColor.B, t, p);
                break;
            case 1: // yellow - green
                rgbColor = Color.FromArgb (hsbColor.A, q, hsbColor.B, p);
                break;
            case 2: // green - cyan
                rgbColor = Color.FromArgb (hsbColor.A, p, hsbColor.B, t);
                break;
            case 3: // cyan - blue
                rgbColor = Color.FromArgb (hsbColor.A, p, q, hsbColor.B);
                break;
            case 4: // blue - magenta
                rgbColor = Color.FromArgb (hsbColor.A, t, p, hsbColor.B);
                break;
            case 5:
            default: // magenta - red
                rgbColor = Color.FromArgb (hsbColor.A, hsbColor.B, p, q);
                break;
        }

        return rgbColor;
    }

    /// <summary>
    /// Convert this <see cref="HsbColor" /> value to an equivalent <see cref="Color" /> value.
    /// </summary>
    /// <remarks>
    /// This method is based on code from http://www.cs.rit.edu/~ncs/color/ by Eugene Vishnevsky.
    /// </remarks>
    /// <returns>An equivalent <see cref="Color" /> struct, compatible with the GDI+ library</returns>
    public Color ToRGB()
    {
        return ToRGB (this);
    }

    /// <summary>
    /// Convert a <see cref="Color" /> value to an equivalent <see cref="HsbColor" /> value.
    /// </summary>
    /// <remarks>
    /// This method is based on code from http://www.cs.rit.edu/~ncs/color/ by Eugene Vishnevsky.
    /// </remarks>
    /// <returns>An equivalent <see cref="HsbColor" /> struct</returns>
    public HsbColor FromRGB()
    {
        return FromRGB (this);
    }

    /// <summary>
    /// Convert a <see cref="Color" /> value to an equivalent <see cref="HsbColor" /> value.
    /// </summary>
    /// <remarks>
    /// This method is based on code from http://www.cs.rit.edu/~ncs/color/ by Eugene Vishnevsky.
    /// </remarks>
    /// <param name="rgbColor">The <see cref="Color" /> struct to be converted</param>
    /// <returns>An equivalent <see cref="HsbColor" /> struct</returns>
    public static HsbColor FromRGB (Color rgbColor)
    {
        double r = (double)rgbColor.R / 255.0;
        double g = (double)rgbColor.G / 255.0;
        double b = (double)rgbColor.B / 255.0;

        double min = Math.Min (Math.Min (r, g), b);
        double max = Math.Max (Math.Max (r, g), b);

        HsbColor hsbColor = new HsbColor (rgbColor.A, 0, 0, 0);

        hsbColor.B = (byte)(max * 255.0 + 0.5);

        double delta = max - min;

        if (max != 0.0)
        {
            hsbColor.S = (byte)(delta / max * 255.0 + 0.5);
        }
        else
        {
            hsbColor.S = 0;
            hsbColor.H = 0;
            return hsbColor;
        }

        double h;
        if (r == max)
        {
            h = (g - b) / delta; // between yellow & magenta
        }
        else if (g == max)
        {
            h = 2 + (b - r) / delta; // between cyan & yellow
        }
        else
        {
            h = 4 + (r - g) / delta; // between magenta & cyan
        }

        hsbColor.H = (byte)(h * 42.5);
        if (hsbColor.H < 0)
        {
            hsbColor.H += 255;
        }

        return hsbColor;
    }
}
